// Copyright (c) 2023 CHANGLEI. All rights reserved.

import 'dart:math';

import 'package:flutter/cupertino.dart';
import 'package:flutter_grasp/src/widget/child_delegate.dart';

/// Created by changlei on 2020-02-13.
///
/// `GrownFlex` is a widget that displays its children in a one-dimensional array.
/// It's a wrapper around the `Row` and `Column` widgets, providing a unified way to handle margins and dividers.
class GrownFlex extends StatelessWidget {
  /// Creates a `GrownFlex` widget.
  ///
  /// The [children] argument must not be null. If [divider] is not null, it will be inserted between each pair of children.
  /// The [crossAxisAlignment] and [textBaseline] arguments must not be null if the [crossAxisAlignment] is set to [CrossAxisAlignment.baseline].
  GrownFlex({
    super.key,
    this.mainAxisAlignment = MainAxisAlignment.start,
    this.crossAxisAlignment = CrossAxisAlignment.center,
    this.mainAxisSize = MainAxisSize.max,
    this.textDirection,
    this.verticalDirection = VerticalDirection.down,
    this.textBaseline,
    this.clipBehavior = Clip.none,
    required List<Widget>? children,
    Widget? divider,
    this.direction = Axis.horizontal,
  })  : assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null),
        childrenDelegate = divider == null || children == null
            ? ChildListDelegate(
                children ?? <Widget>[],
                addAutomaticKeepAlives: false,
                addRepaintBoundaries: false,
                addSemanticIndexes: false,
              )
            : ChildBuilderDelegate(
                (context, index) {
                  final itemIndex = index ~/ 2;
                  Widget widget;
                  if (index.isEven) {
                    widget = children[itemIndex];
                  } else {
                    widget = divider;
                  }
                  return widget;
                },
                childCount: _computeActualChildCount(children.length),
                addAutomaticKeepAlives: false,
                addRepaintBoundaries: false,
                addSemanticIndexes: false,
                semanticIndexCallback: (_, index) {
                  return index.isEven ? index ~/ 2 : null;
                },
              );

  /// Creates a `GrownFlex` widget with space between each child.
  ///
  /// The [spacing] argument must be non-negative. If it's greater than 0, a `SizedBox` widget with the specified width or height will be inserted between each pair of children.
  @Deprecated(r'Use `Flex` instead.')
  factory GrownFlex.spacing({
    Key? key,
    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
    MainAxisSize mainAxisSize = MainAxisSize.max,
    TextDirection? textDirection,
    VerticalDirection verticalDirection = VerticalDirection.down,
    TextBaseline? textBaseline,
    Clip clipBehavior = Clip.none,
    required List<Widget>? children,
    double? spacing = 0,
    Axis direction = Axis.horizontal,
  }) {
    assert(spacing == null || spacing >= 0);
    Widget? divider;
    if (spacing != null && spacing > 0) {
      divider = SizedBox(
        width: direction == Axis.vertical ? null : spacing,
        height: direction == Axis.vertical ? spacing : null,
      );
    }
    return GrownFlex(
      key: key,
      mainAxisAlignment: mainAxisAlignment,
      crossAxisAlignment: crossAxisAlignment,
      mainAxisSize: mainAxisSize,
      textDirection: textDirection,
      verticalDirection: verticalDirection,
      textBaseline: textBaseline,
      clipBehavior: clipBehavior,
      direction: direction,
      divider: divider,
      children: children,
    );
  }

  /// Creates a `GrownFlex` widget with a builder function for creating each child.
  ///
  /// The [itemBuilder] argument must not be null. The [itemCount] argument must be non-negative.
  GrownFlex.builder({
    super.key,
    this.mainAxisAlignment = MainAxisAlignment.start,
    this.crossAxisAlignment = CrossAxisAlignment.center,
    this.mainAxisSize = MainAxisSize.max,
    required IndexedWidgetBuilder itemBuilder,
    required int itemCount,
    this.textDirection,
    this.verticalDirection = VerticalDirection.down,
    this.textBaseline,
    this.clipBehavior = Clip.none,
    this.direction = Axis.horizontal,
  })  : assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null),
        assert(itemCount >= 0),
        childrenDelegate = ChildBuilderDelegate(
          itemBuilder,
          childCount: itemCount,
          addAutomaticKeepAlives: false,
          addRepaintBoundaries: false,
          addSemanticIndexes: false,
        );

  /// Creates a `GrownFlex` widget with a builder function for creating each child and a separate builder function for creating separators.
  ///
  /// The [itemBuilder] and [separatorBuilder] arguments must not be null. The [itemCount] argument must be non-negative.
  GrownFlex.separated({
    super.key,
    this.mainAxisAlignment = MainAxisAlignment.start,
    this.crossAxisAlignment = CrossAxisAlignment.center,
    this.mainAxisSize = MainAxisSize.max,
    required IndexedWidgetBuilder itemBuilder,
    required IndexedWidgetBuilder separatorBuilder,
    required int itemCount,
    this.textDirection,
    this.verticalDirection = VerticalDirection.down,
    this.textBaseline,
    this.clipBehavior = Clip.none,
    this.direction = Axis.horizontal,
  })  : assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null),
        assert(itemCount >= 0),
        childrenDelegate = ChildBuilderDelegate(
          (context, index) {
            final itemIndex = index ~/ 2;
            Widget widget;
            if (index.isEven) {
              widget = itemBuilder(context, itemIndex);
            } else {
              widget = separatorBuilder(context, itemIndex);
            }
            return widget;
          },
          childCount: _computeActualChildCount(itemCount),
          addAutomaticKeepAlives: false,
          addRepaintBoundaries: false,
          addSemanticIndexes: false,
          semanticIndexCallback: (_, index) {
            return index.isEven ? index ~/ 2 : null;
          },
        );

  /// Creates a `GrownFlex` widget with a custom delegate for creating children.
  ///
  /// The [childrenDelegate] argument must not be null.
  const GrownFlex.custom({
    super.key,
    this.mainAxisAlignment = MainAxisAlignment.start,
    this.crossAxisAlignment = CrossAxisAlignment.center,
    this.mainAxisSize = MainAxisSize.max,
    required this.childrenDelegate,
    this.textDirection,
    this.verticalDirection = VerticalDirection.down,
    this.textBaseline,
    this.clipBehavior = Clip.none,
    this.direction = Axis.horizontal,
  });

  /// {@template flutter.grasp.GrownFlex.mainAxisAlignment}
  /// How the children should be placed along the main axis.
  ///
  /// For example, [MainAxisAlignment.start], the default, places the children
  /// at the start (i.e., the left for a [GrownRow] or the top for a [GrownColumn]) of the
  /// main axis.
  /// {@endtemplate}
  final MainAxisAlignment mainAxisAlignment;

  /// {@template flutter.grasp.GrownFlex.mainAxisSize}
  /// How much space should be occupied in the main axis.
  ///
  /// After allocating space to children, there might be some remaining free
  /// space. This value controls whether to maximize or minimize the amount of
  /// free space, subject to the incoming layout constraints.
  ///
  /// If some children have a non-zero flex factors (and none have a fit of
  /// [FlexFit.loose]), they will expand to consume all the available space and
  /// there will be no remaining free space to maximize or minimize, making this
  /// value irrelevant to the final layout.
  /// {@endtemplate}
  final MainAxisSize mainAxisSize;

  /// {@template flutter.grasp.GrownFlex.crossAxisAlignment}
  /// How the children should be placed along the cross axis.
  ///
  /// For example, [CrossAxisAlignment.center], the default, centers the
  /// children in the cross axis (e.g., horizontally for a [GrownColumn]).
  ///
  /// When the cross axis is vertical (as for a [GrownRow]) and the children
  /// contain text, consider using [CrossAxisAlignment.baseline] instead.
  /// This typically produces better visual results if the different children
  /// have text with different font metrics, for example because they differ in
  /// [TextStyle.fontSize] or other [TextStyle] properties, or because
  /// they use different fonts due to being written in different scripts.
  /// {@endtemplate}
  final CrossAxisAlignment crossAxisAlignment;

  /// {@template flutter.grasp.GrownFlex.textDirection}
  /// Determines the order to lay children out horizontally and how to interpret
  /// `start` and `end` in the horizontal direction.
  ///
  /// Defaults to the ambient [Directionality].
  ///
  /// If [textDirection] is [TextDirection.rtl], then the direction in which
  /// text flows starts from right to left. Otherwise, if [textDirection] is
  /// [TextDirection.ltr], then the direction in which text flows starts from
  /// left to right.
  ///
  /// If the [direction] is [Axis.horizontal], this controls the order in which
  /// the children are positioned (left-to-right or right-to-left), and the
  /// meaning of the [mainAxisAlignment] property's [MainAxisAlignment.start] and
  /// [MainAxisAlignment.end] values.
  ///
  /// If the [direction] is [Axis.horizontal], and either the
  /// [mainAxisAlignment] is either [MainAxisAlignment.start] or
  /// [MainAxisAlignment.end], or there's more than one child, then the
  /// [textDirection] (or the ambient [Directionality]) must not be null.
  ///
  /// If the [direction] is [Axis.vertical], this controls the meaning of the
  /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and
  /// [CrossAxisAlignment.end] values.
  ///
  /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is
  /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
  /// [textDirection] (or the ambient [Directionality]) must not be null.
  /// {@endtemplate}
  final TextDirection? textDirection;

  /// {@template flutter.grasp.GrownFlex.verticalDirection}
  /// Determines the order to lay children out vertically and how to interpret
  /// `start` and `end` in the vertical direction.
  ///
  /// Defaults to [VerticalDirection.down].
  ///
  /// If the [direction] is [Axis.vertical], this controls which order children
  /// are painted in (down or up), the meaning of the [mainAxisAlignment]
  /// property's [MainAxisAlignment.start] and [MainAxisAlignment.end] values.
  ///
  /// If the [direction] is [Axis.vertical], and either the [mainAxisAlignment]
  /// is either [MainAxisAlignment.start] or [MainAxisAlignment.end], or there's
  /// more than one child, then the [verticalDirection] must not be null.
  ///
  /// If the [direction] is [Axis.horizontal], this controls the meaning of the
  /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and
  /// [CrossAxisAlignment.end] values.
  ///
  /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is
  /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
  /// [verticalDirection] must not be null.
  /// {@endtemplate}
  final VerticalDirection verticalDirection;

  /// {@template flutter.grasp.GrownFlex.textBaseline}
  /// If aligning items according to their baseline, which baseline to use.
  ///
  /// This must be set if using baseline alignment. There is no default because there is no
  /// way for the framework to know the correct baseline _a priori_.
  /// {@endtemplate}
  final TextBaseline? textBaseline;

  /// {@macro flutter.material.Material.clipBehavior}
  ///
  /// Defaults to [Clip.none].
  final Clip clipBehavior;

  /// {@template flutter.grasp.GrownFlex.direction}
  /// The direction to use as the main axis.
  ///
  /// If you know the axis in advance, then consider using a [GrownRow] (if it's
  /// horizontal) or [GrownColumn] (if it's vertical) instead of a [GrownFlex], since that
  /// will be less verbose. (For [GrownRow] and [GrownColumn] this property is fixed to
  /// the appropriate axis.)
  /// {@endtemplate}
  final Axis direction;

  /// {@template flutter.grasp.GrownFlex.childrenDelegate}
  /// A delegate that provides the children for the [GrownFlex].
  ///
  /// The [GrownFlex.custom] constructor lets you specify this delegate
  /// explicitly. The [GrownFlex] and [GrownFlex.builder] constructors create a
  /// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder],
  /// respectively.
  /// {@endtemplate}
  final ChildDelegate childrenDelegate;

  @override
  Widget build(BuildContext context) {
    final childCount = childrenDelegate.estimatedChildCount ?? 0;
    final children = List<Widget>.generate(childCount, (index) {
      return childrenDelegate.build(context, index)!;
    });

    return Flex(
      mainAxisAlignment: mainAxisAlignment,
      mainAxisSize: mainAxisSize,
      crossAxisAlignment: crossAxisAlignment,
      textDirection: textDirection,
      verticalDirection: verticalDirection,
      textBaseline: textBaseline,
      clipBehavior: clipBehavior,
      direction: direction,
      children: children,
    );
  }

  // Helper method to compute the actual child count for the separated constructor.
  static int _computeActualChildCount(int itemCount) {
    return max(0, itemCount * 2 - 1);
  }
}
